package com.seafwg;

/**
 * @Author seafwg
 * @Date 2022/7/27 18:02
 * @Version 1.0
 * @Description 多线程练习：创建三个窗口卖票，总票数为100张.使用继承Thread类的方式
 * 问题：出现重票，错票。 ---> 出现线程安全问题
 *  程序运行分析：
 *      多线程操作共享的数据ticket，开始的时候，当一个线程进行买票时先抢到cpu资源，该线程进行对数据操作，
 *      同时其他资源也抢到了cpu资源，其他线程也进行了操作.
 *  问题解决：当一个线程a在操作ticket的时候，其他线程不得参与进来操作ticket，知道a线程操作完之后，其他线程才可以操作。
 *
 * 解决方法：使用同步机制解决
 *  方式一：同步代码块：
 *    synchronized(同步监视器) {
 *        //需要被同步的代码，也就是线程共享的数据被操作的代码
 *    }
 *    同步监视器：也称"锁"，任何一个类都可以充当锁，一般习惯使用当前类xxx.class
 *    继承Thread类和实现Runnable接口时，由于创建对象不同，锁的意义也不同：
 *      1.继承Thread的子类创建线程时，要创建多个子类对象，锁，只能是子类的类
 *      2.实现Runnable接口的子类创建线程时，共用一个子类对象，锁可以是子类的类，也可以是this
 *
 *  方式二：同步方法：使用synchronized修饰的方法编写操作线程的数据
 *      抽离一个方法专门编写操作共享数据的操作。
 *  建议使用同步代码块的形式，效率高于同步方法
 */

public class ThreadDemo2 {

    public static void main(String[] args) {
        // 方式一测试
        SellTicketWindow01 window1 = new SellTicketWindow01();
        // window1.start();
        SellTicketWindow01 window2 = new SellTicketWindow01();
        // window2.start();
        SellTicketWindow01 window3 = new SellTicketWindow01();
        // window3.start();
        // 测试完美输出没有重票和错票

        // 方式二测试
        SellTicketWindow31 s = new SellTicketWindow31();
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

/**
 * 方式一：使用继承Thread的方式创建线程
 */
class SellTicketWindow01 extends Thread{
    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            // 使用同步方法解决线程安全问题
            operateTicket();
        }
    }

    /**
     * 继承Thread类需要静态方法，确保调用
     */
    private static synchronized void operateTicket() {
        if (ticket > 0) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":窗口买票，票号为：" + ticket);
            ticket--;
        }
    }
}

/**
 * 方式二：使用实现Runnable接口的方式创建线程
 */
class SellTicketWindow31 implements Runnable{
    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            operateTicket();
        }
    }

    private synchronized void operateTicket() {
        if (ticket > 0) {
            // 线程的睡眠相当于增大了阻塞的概率，让问题出现的概率增大
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":窗口买票，票号为：" + ticket);
            ticket--;
        }
    }
}
